<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>打开控制台查看结果</div>
    
    <script>
        console.log('=====暂时性死区======');
        /* 
        暂时性死区也就是变量声明到声明完成的区块，这个区块是一个封闭的作用域，直到声明完成。
        如果在变量声明之前使用该变量，那么该变量是不可用的，也就被称为暂时性死区。
        var 没有暂时性死区， 因为var会变量提升
        let const 有块级作用域，变量不会提升，存在暂时性死区
        */
       // console.log(a); // 报错 暂时性死区 Cannot access 'a' before initialization
       // let a = '东方不败' 

       // console.log(b); // 报错 暂时性死区 Cannot access 'b' before initialization
       // const b = '东方不败'


       console.log(c);   // undefined 因为var存在变量提升
       var c = '东方求败'  

       /* 
       ES6 规定，如果代码区块中存在 let 和 const 命令声明的变量，
       这个区块对这些变量从一开始就形成了封闭作用域，直到声明语句完成，
       这些变量才能被访问（获取或设置），否则会报错ReferenceError。
       这在语法上称为“暂时性死区”（英temporal dead zone，简 TDZ），
       即代码块开始到变量声明语句完成之间的区域。
       */

        console.log('=======函数作用域========');

        console.log('===案例1===');
        /* 
        一旦设置了参数的默认值，函数进行声明初始化时，参数就会形成一个单独的作用域
        等初始化结束，这个作用域就会消失，这种语法在不设置参数默认值时不会出现。
        （暂时性死区）
        */
        /*  
        在这个作用域里，y默认的x变量指向第一个参数x,而不是全局变量x
        这里调用f函数，向x传递了数值2，y = x 那么 y = 2,打印结果为2
         */
        var x = 1
        function f(x,y = x){
            console.log(y);
        }
        f(2)  // 2
       

        console.log('===案例2===');
        /* 
          调用f2函数，由于未给f2函数任何参数，并且y2 = x2 形成一个单独的作用域
          在这个作用域里x2并未定义，所以x2指向的是外层全局变量x2
          y2 = x2 也就是 y2 = 1
          函数内部的x2并没有起到任何作用
        */

        let x2 = 1
        function f2(y2 = x2){
            let x2 = 2
            console.log(y2);
        }
        f2()  // 1

        /* 但如果去掉全局变量x2则会报错，因为变量未声明，给y2赋值了一个未声明的变量，报错 */


        // 下面这个写法也会报错
        /* 
          由于函数的参数有默认值会产生单独的作用域，
          那么这个作用域内，执行的结果为 let xx = xx
          给 xx 赋值一个为声明的 xx 报错
          (暂时性死区)
        */
        // var xx = 1
        // function fxx(xx = xx){
        //     console.log(xx);
        // }
        // fxx()


        /* 如果函数的默认参数是函数，该函数的作用域也要遵循这个规则
           函数bar的参数func默认值是一个匿名函数，返回值为变量foo。
           函数参数形成单独的作用域里面，并没有定义变量foo，所以foo指向外层全局变量foo
        */
        let foo = 'out'

        function bar(func = () => foo){
            let foo = 'come'
            console.log(func());
        }
        bar()  // out

        /* 如果去掉全局变量 foo='out' 报错，赋值了一个未声明的变量 */

        /* 应用
           可以利用参数默认值，如果省略默认值则抛出一个错误
        */
    //    function throwErr(){
    //     throw new Error('参数不得省略')
    //    }
    //    function omits(mustfn = throwErr()){
    //      return mustfn
    //    }  
    //    console.log(omits()); // 未传参抛出错误 ： 参数不得省略
        
    /* 如果将参数默认值设置为 undefined 则表示该参数是可以省略的 */

    console.log('============rest参数============');
    /* arguments可以获得函数的参数值以及函数信息（name\length）等  */
    function au(arr){
        console.log('arguments:',arguments);
        // 通过数组方法对函数参数进行排序
        console.log(Array.from(arguments).sort());  // [1, 2, 3, 4]
    }
    au(2,1,4,3)


    // 参数的剩余运算符
    function residue(...val){
        console.log(val);  // [1,2,3]
    }
    residue(1,2,3)


    // 剩余运算符只能放到最后一位，否则报错
    // function residue2(...val,b){}  // 剩余运算符不是最后一位，报错
    function residue3(c,...val){
        console.log(c,val);  // 1 [2,3,4,5]
    }
    residue3(1,2,3,4,5)


    let au2 = (...val) => val.sort()
    console.log(au2(2,1,4,3));  // [1, 2, 3, 4]


    console.log('===========严格模式============');
    /* 
       ES5开始，函数内部可以设定为严格模式： function s(){ 'use strict'// 严格模式 }
       ES6做了修改，规定只要函数参数使用了默认值、解构赋值、扩展运算符，那么函数内部就不能显示设定为严格模式，否则报错
    */

    // es5严格模式
    function s(){
        'use strict'   // 严格模式
        // 代码.....
    }

    // es6严格模式  报错，因为设置了函数默认值
    // function s2(a,b = a){
    //     'use strict'
    // }

    // 报错，使用了解构赋值
    // const s3 = function({a,b}){
    //     'use strict'
    // }

    // 报错，使用了剩余运算符
    // const s4 = (...a) => {
    //     'use strict'
    // }

    /* es6这样设置的原因是，函数内部的严格模式应该同样适用于函数体和函数参数，
       但是，函数执行的时候会先执行参数，再执行函数体，这样就有一些不严谨的情况，
       只有函数体中才能知道参数是否应该以严格模式执行，但是函数的参数确是先执行，
       所以es6修改了函数参数关于严格模式的行为。
    */

    // function s5(val = 070){
    //     'use strict'
    //     return val
    // }
    // s5()  // 报错
    // 这一段，函数的默认值是八进制070，严格模式下不能使用前缀0表示八进制，所以报错。
    // 但实际上是因为函数设置了默认参数的原因，函数先执行参数，再进函数体，由于es6限制，报错。

    // 有两种方法可以规避这种限制
    // 第一种：设置全局严格模式
    'use strict'
    function s6(val = 100){
        console.log(val);
    }
    s6()  // 100

    
    // 第二种方法：把函数嵌套在一个无参数的立即执行函数里
    // 匿名函数的调用方法：(function(val = 200){return val})(),将整个return的函数用()套起来，尾部加一个()调用即可
    const s7 = () => {
        'use strict'
        let a;
        return (function(val = 200){ return val })()
    }
    console.log(s7());







    </script>
</body>
</html>